const fs = require('fs');
const path = require("path");
const ecs = require('./aws/ecs-restApi');
const ecr = require('./aws/ecr-restApi');
const vpc = require('./aws/vpc-restApi');
const ec2 = require('./aws/ec2-restApi');
const iam = require('./aws/iam-restApi');
const lambda = require('./aws/lambda-restApi');
const s3 = require('./aws/s3-restApi');
const sts = require('./aws/sts-restApi');
const apigateway = require('./aws/apigateway-restApi');
const servicediscovery = require('./aws/servicediscovery-restApi');
const d = {};

d.createKeyPair = async(keyName) => {
  const existed = await ec2.describeKeyPairs({KeyNames:[keyName]});
  if (existed.KeyPairs.length <= 0) {
    const params = {
      KeyName: keyName
    };
    console.log(`creating KeyPair ${keyName}...`);
    const keyPair = await ec2.createKeyPair(params);
    console.log(`created KeyPair ${keyName}`);
    const keyPairLocation = `./${keyPair.KeyName}.${keyPair.KeyPairId}.pem`;
    fs.writeFileSync(keyPairLocation, keyPair.KeyMaterial);
    console.log(`KeyPair ${keyName} saved to ${keyPairLocation}`);
    return keyPair;
  } else {
    console.log(`existed KeyPairs ${keyName},return the first one...`);
    return existed.KeyPairs[0];
  }
};

d.createOrGetEC2SecurityGroup = async(vpcId, securityGroupName, desc="") => {

  const describeEC2SecurityGroupsResp = await vpc.describeSecurityGroups({Filters : [
      {Name: 'group-name', Values: [securityGroupName]},
      {Name: 'vpc-id', Values: [vpcId]},
    ]});
  if (describeEC2SecurityGroupsResp.SecurityGroups.length > 0){
    return {
      Id: describeEC2SecurityGroupsResp.SecurityGroups[0].GroupId,
      Name: describeEC2SecurityGroupsResp.SecurityGroups[0].GroupName
    };
  }

  console.log(`creating SecurityGroup ${securityGroupName}...`);
  const createEC2SecurityGroup = await vpc.createSecurityGroup({
    Description: desc,
    GroupName: securityGroupName,
    VpcId: vpcId
  });
  console.log(`created SecurityGroup ${securityGroupName}`);
  const groupId = createEC2SecurityGroup.GroupId;
  // return createOrGetEC2SecurityGroup;

  const applyIngressRule_params = {
    GroupId: groupId,
    IpPermissions: [
      {
        FromPort: 22,
        IpProtocol: "tcp",
        IpRanges: [
          {
            CidrIp: "0.0.0.0/0",
            Description: "SSH access from the LA office"
          }
        ],
        Ipv6Ranges: [
          {
            CidrIpv6: "::/0"
          }
        ],
        ToPort: 22
      },
      {
        FromPort: 1000,
        IpProtocol: "tcp",
        IpRanges: [
          {
            CidrIp: "0.0.0.0/0",
            Description: "Application available tcp tunnels"
          }
        ],
        Ipv6Ranges: [
          {
            CidrIpv6: "::/0"
          }
        ],
        ToPort: 60000
      },
      {
        FromPort: -1,
        IpProtocol: "icmp",
        IpRanges: [
          {
            CidrIp: "0.0.0.0/0",
            Description: "Enable ping command"
          },
        ],
        Ipv6Ranges: [
          {
            CidrIpv6: "::/0"
          }
        ],
        ToPort: -1
      }
    ]
  };
  console.log(`creating SecurityGroupIngress ${groupId}...`);
  const applyIngressRule = await vpc.authorizeSecurityGroupIngress(applyIngressRule_params);
  console.log(`created SecurityGroupIngress ${groupId}`);
  // console.log(applyIngressRule);
  return {
    Id: createEC2SecurityGroup.GroupId,
    Name: securityGroupName
  };
  // return createOrGetEC2SecurityGroup;
};

d.createOrGetRole = async(name, assumeRolePolicyDocument, policyArnList) => {
  try {
    const getRoleResp = await iam.getRole({RoleName: name});
    return {
      Id: getRoleResp.Role.RoleId,
      Arn: getRoleResp.Role.Arn,
      Name: getRoleResp.Role.RoleName,
      Path: getRoleResp.Role.Path
    };
  } catch (error) {
    if (error.code === "NoSuchEntity") {
      // step1: create IAM role
      const creatRoleResp = await iam.createRole({
        AssumeRolePolicyDocument: JSON.stringify(assumeRolePolicyDocument),
        Path: "/",
        RoleName: name
      });

      // step2: attach aws-managed policies to the role
      for (const policyArn of policyArnList) {
        await iam.attachRolePolicy({
          PolicyArn: policyArn,
          RoleName: name
        });
      }

      return {
        Id: creatRoleResp.Role.RoleId,
        Arn: creatRoleResp.Role.Arn,
        Name: creatRoleResp.Role.RoleName,
        Path: creatRoleResp.Role.Path
      }
    }
    throw error;
  }
};

// d.createOrGetLambdaRole = async() => {
//   return d.createOrGetRole("pt-lambda-role",
//     {
//       "Version": "2012-10-17",
//       "Statement": [
//         {
//           "Effect": "Allow",
//           "Principal": {
//             "Service": "lambda.amazonaws.com"
//           },
//           "Action": "sts:AssumeRole"
//         }
//       ]
//     },
//     [
//       "arn:aws:iam::aws:policy/AmazonSQSFullAccess",
//       "arn:aws:iam::aws:policy/AWSLambdaFullAccess",
//       "arn:aws:iam::aws:policy/AmazonAPIGatewayInvokeFullAccess",
//       "arn:aws:iam::aws:policy/AmazonElasticMapReduceFullAccess",
//       "arn:aws:iam::aws:policy/AmazonSESFullAccess",
//       "arn:aws:iam::aws:policy/AmazonECS_FullAccess",
//       "arn:aws:iam::aws:policy/AmazonCognitoPowerUser",
//       "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole",
//       "arn:aws:iam::aws:policy/AmazonSNSFullAccess",
//       "arn:aws:iam::aws:policy/service-role/AWSLambdaRole",
//     ]);
//   // const roleName = "pt-lambda-role";
//   // // step1: create IAM role
//   // const creatRoleResp = await iam.createRole({
//   //   AssumeRolePolicyDocument: JSON.stringify({
//   //     "Version": "2012-10-17",
//   //     "Statement": [
//   //       {
//   //         "Effect": "Allow",
//   //         "Principal": {
//   //           "Service": "lambda.amazonaws.com"
//   //         },
//   //         "Action": "sts:AssumeRole"
//   //       }
//   //     ]
//   //   }),
//   //   Path: "/",
//   //   RoleName: roleName
//   // });
//   //
//   // // step2: attach aws-managed policies to the role
//   // const policyArnList = [
//   //   "arn:aws:iam::aws:policy/AmazonSQSFullAccess",
//   //   "arn:aws:iam::aws:policy/AWSLambdaFullAccess",
//   //   "arn:aws:iam::aws:policy/AmazonAPIGatewayInvokeFullAccess",
//   //   "arn:aws:iam::aws:policy/AmazonElasticMapReduceFullAccess",
//   //   "arn:aws:iam::aws:policy/AmazonSESFullAccess",
//   //   "arn:aws:iam::aws:policy/AmazonECS_FullAccess",
//   //   "arn:aws:iam::aws:policy/AmazonCognitoPowerUser",
//   //   "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole",
//   //   "arn:aws:iam::aws:policy/AmazonSNSFullAccess",
//   //   "arn:aws:iam::aws:policy/service-role/AWSLambdaRole",
//   // ];
//   // for (const policyArn of policyArnList) {
//   //   await iam.attachRolePolicy({
//   //     PolicyArn: policyArn,
//   //     RoleName: roleName
//   //   });
//   // }
// };

d.createOrGetS3Bucket = async(s3BucketName) => {
  // check existence of s3 bucket, if not create it
  const listBucketsResp = await s3.listBuckets({});
  const existedBuckets = listBucketsResp.Buckets.filter(item => {
    return item.Name === s3BucketName;
  });

  if (existedBuckets.length <= 0) {
    const createBucketResp = await s3.createBucket({
      Bucket: s3BucketName,
      CreateBucketConfiguration: {
        LocationConstraint: "us-east-2"
      },
    });
  } else {
    s3BucketName = existedBuckets[0].Name;
  }
  return {
    Name: s3BucketName
  };
};

d.createOrGetS3EmptyFolder = async(bucketName, objectPath) => {
  const putObjectResp = await s3.putObject({
    Bucket: bucketName,
    Key: `${objectPath}/.keep`,
  });
  return putObjectResp;
};

d.createOrGetS3ObjectFromLocalPath = async(bucketName, localPath, objectPath) => {
  const putObjectResp = await s3.putObject({
    Body: fs.createReadStream(localPath),
    Bucket: bucketName,
    Key: objectPath,
  });
  return putObjectResp;
};

// d.createOrGetLambdaLayer = async(name, desc, localPackagePath) => {
//   let s3BucketName = "pt-lambda-layer-version-uat";
//   // check existence of s3 bucket, if not create it
//   await d.createOrGetS3Bucket(s3BucketName);
//
//   // step1: upload local package to s3
//   const putObjectResp = await s3.putObject({
//     Body: fs.createReadStream(localPackagePath),
//     Bucket: s3BucketName,
//     Key: path.basename(localPackagePath),
//   });
//
//   // step2: publish layer version
//   const params = {
//     CompatibleRuntimes: [
//       "nodejs12.x",
//     ],
//     Content: {
//       S3Bucket: s3BucketName,
//       S3Key: path.basename(localPackagePath)
//     },
//     Description: desc,
//     LayerName: name,
//     LicenseInfo: "MIT"
//   };
//   const r = await lambda.publishLayerVersion(params);
//   return {
//     LayerArn: r.LayerArn,
//     LayerVersionArn: r.LayerVersionArn,
//     Version: r.Version,
//     Description: r.Description
//   };
// };

d.getCallerIdentity = async() => {
  return await sts.getCallerIdentity()
};

d.findVpcByName = async (name) => {
  // const name = "PT-Net-UAT";
  const describeVpcsResp = await vpc.describeVpcs({});
  const filteredVpcs = describeVpcsResp.Vpcs.filter(item => {
    const t = item.Tags.filter(i2 => i2.Value === name);
    return t.length > 0;
  });
  if (filteredVpcs.length > 0) {
    // get subnets
    const describeSubnetsResp = await vpc.describeSubnets({Filters: [
        {
          Name: "vpc-id",
          Values: [filteredVpcs[0].VpcId]
        }
      ]});
    const publicSubnets = describeSubnetsResp.Subnets.filter(item => {
      const filteredPublicSubnets = item.Tags.filter(i2 => i2.Key === "Name" && i2.Value.indexOf("Public") >= 0);
      return filteredPublicSubnets.length > 0;
    });
    const protectedSubnets = describeSubnetsResp.Subnets.filter(item => {
      const filteredPublicSubnets = item.Tags.filter(i2 => i2.Key === "Name" && i2.Value.indexOf("Protected") >= 0);
      return filteredPublicSubnets.length > 0;
    });
    const privateSubnets = describeSubnetsResp.Subnets.filter(item => {
      const filteredPublicSubnets = item.Tags.filter(i2 => i2.Key === "Name" && i2.Value.indexOf("Private") >= 0);
      return filteredPublicSubnets.length > 0;
    });

    const nameTags = filteredVpcs[0].Tags.filter(item => item.Key === "Name");
    return {
      Id: filteredVpcs[0].VpcId,
      Name: nameTags[0].Value || "",
      PublicSubnetIds: publicSubnets.map(i => i.SubnetId),
      ProtectedSubnetIds: protectedSubnets.map(i => i.SubnetId),
      PrivateSubnetIds: privateSubnets.map(i => i.SubnetId)
    }
  } else {
    return null;
  }
};

d.findVpcLinkByName = async (name) => {
  const vpcLinks = await apigateway.getVpcLinks({});
  const vpcLink = vpcLinks.items.filter((item) => {
    return item.name === name;
  });
  if (vpcLink.length > 0) {return vpcLink[0];}
  else {return null;}
};

d.findEC2InstancesByName = async (instanceName, vpcId, subnetId) => {
  const filters = [{Name: "tag:Name",Values: [instanceName]}];
  if (vpcId) {
    filters.push({Name: "vpc-id", Values: [vpcId]});
  }
  if (subnetId) {
    filters.push({Name: "subnet-id", Values: [subnetId]});
  }
  const ec2instances = await ec2.describeInstances({Filters : filters});
  let ec2list = [];
  ec2instances.Reservations.forEach(r => {
    ec2list = ec2list.concat(...r.Instances);
  });
  return ec2list;
  // const ec2list = await ec2instances.Reservations.filter((item) => {
  //   if (item.Instances[0].Tags) {
  //     const tags = item.Instances[0].Tags;
  //     const nameTags = tags.filter((tag) => {
  //       return tag.Key === "Name";
  //     });
  //     if (nameTags.length > 0) {
  //       const name = nameTags[0].Value;
  //       return name === instanceName;
  //     } else { return false;}
  //   } else { return false;}
  // });
  // return ec2list;
};

d.findClusterByName = async (name) => {
  const clusters = await ecs.describeClusters({clusters: [name]});
  if (clusters.clusters.length > 0) {return clusters.clusters[0];}
  else {return null;}
};

d.findLambdasById = async (ids) => {
  let lambdaFunctions = [];
  let resp;
  do {
    if (resp && resp.NextMarker) {
      resp = await lambda.listFunctions({Marker: resp.NextMarker});
    } else {
      resp = await lambda.listFunctions({});
    }
    lambdaFunctions = lambdaFunctions.concat(resp.Functions);
  } while (resp.NextMarker);
  const result = lambdaFunctions.filter(item => {
    for (id of ids){
      if (item.FunctionName.indexOf(id) > -1){
        item.Id = id;
        return item;
      }
    }
  }).map(item => {
    return {
      Id: item.Id,
      Name: item.FunctionName,
      Arn: item.FunctionArn
    }
  });
  return result;
};

d.findApiGatewaysByName = async(name) => {
  const getRestApisResp = await apigateway.getRestApis({limit: "100"});
  if (getRestApisResp.items.length >= 100) {
    console.warn("exceeded the limit of getRestApis, the results might miss some items.")
  }
  const filteredApiGateways = getRestApisResp.items.filter(item => {
    return item.name === name;
  });
  if (filteredApiGateways.length > 0){
    return {
      Id: filteredApiGateways[0].id,
      Name: filteredApiGateways[0].name,
      Types: filteredApiGateways[0].endpointConfiguration.types
    }
  } else {
    return null;
  }
};

d.findRolesByName = async(name) => {
  const getRoleResp = await iam.getRole({RoleName: name});
  return {
    Id: getRoleResp.Role.RoleId,
    Arn: getRoleResp.Role.Arn,
    Name: getRoleResp.Role.RoleName,
    Path: getRoleResp.Role.Path
  };
};

d.findRepositoryByName = async (repositoryName) => {
  const repositories = await ecr.describeRepositories({});
  const repository = await repositories.repositories.filter((item) => {
    return item.repositoryName === repositoryName;
  });
  if (repository.length > 0) {return repository[0];}
  else {return null;}
};

d.findNamespaceByName = async (namespaceName) => {
  const namespaces = await servicediscovery.listNamespaces({});
  const namespace = await namespaces.Namespaces.filter((item) => {
    return item.Name === namespaceName;
  });
  if (namespace.length > 0) {return namespace[0];}
  else {return null;}
};

d.findServiceDiscoveryByName = async (serviceDiscoveryName) => {
  const serviceDiscoverys = await servicediscovery.listServices({});
  const serviceDiscovery = await serviceDiscoverys.Services.filter((item) => {
    return item.Name === serviceDiscoveryName;
  });
  if (serviceDiscovery.length > 0) {return serviceDiscovery[0];}
  else {return null;}
};

module.exports = d;
if (require.main === module) {
  (async () => {
    // const binaryString = fs.createReadStream('E:\\BuildingMaterials.zip');
    // const putObjectResp = await s3.putObject({
    //   Body: binaryString,
    //   Bucket: "testjimmy",
    //   Key: `nodejs.zip`,
    // });
    // console.log(putObjectResp)
    // const abc = await module.exports.getLayerVersionByArn({Arn: "arn:aws:lambda:us-east-2:599885534814:layer:pt-common-packages:3"});
    // console.log(abc);
    // const r = await d.findVpcByName("PT-Net-UAT");
    // console.log(r);
    // const r = await d.findApiGatewaysByName("pt-private-gateway");
    // console.log(r);
    // const r = await d.findRolesByName("ecsTaskExecutionRole2");
    // console.log(r);
    const describeEC2SecurityGroupsResp = await vpc.describeSecurityGroups({Filters : [
        {Name: 'group-name', Values: ["Bastion-SecurityGroup"]},
        {Name: 'vpc-id', Values: ["vpc-06eeaf89a5631e90a"]},
      ]});
    console.log(describeEC2SecurityGroupsResp);
  })();
}